package com.juphoon.oauth.controller;

import com.juphoon.oauth.core.config.Const;
import com.juphoon.oauth.core.listener.AuthenticationListener;
import com.juphoon.oauth.core.properties.SecurityProperties;
import com.juphoon.oauth.core.utils.CheckMobile;
import com.juphoon.oauth.exception.SecurityAuthenticationException;
import com.juphoon.oauth.utils.OAuth2TokenUtils;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Map;
import java.util.Set;

/**
 * 自定义刷选token
 *
 * @author rongbin.huang
 * @create 2018-07-02 下午10:07
 **/
@RestController
public class RefreshTokenController {

    private static final String REFRESH_TOKEN = "refresh_token";

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private AuthorizationServerTokenServices authorizationServerTokenServices;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private OAuth2TokenUtils oAuth2TokenUtils;

    @Autowired
    private AuthenticationListener authenticationListener;


    @RequestMapping("/oauth/refresh")
    public OAuth2AccessToken refreshToken(@RequestParam Map<String, String> parameters,
                                          HttpServletRequest request) throws IOException {
        if (!isRefreshTokenRequest(parameters)) {
            throw new SecurityAuthenticationException("not refresh_token request");
        }
        String username = parameters.get(securityProperties.getOauth2().getUsernameParameter());
        String clientId = getClientId(request);
        ClientDetails authenticatedClient = clientDetailsService.loadClientByClientId(clientId);
        TokenRequest tokenRequest = createTokenRequest(parameters, authenticatedClient);
        OAuth2AccessToken token = authorizationServerTokenServices.refreshAccessToken(parameters.get(REFRESH_TOKEN), tokenRequest);

        if (Const.OAuth2StoreType.REDIS.equals(securityProperties.getOauth2().getStoreType())) {
            if (StringUtils.isEmpty(username)) {
                throw new SecurityAuthenticationException("need param " + securityProperties.getOauth2().getUsernameParameter());
            }
            String agent = CheckMobile.getUserAgent(request.getHeader("user-agent"));
            String oldAccessToken = redisTemplate.opsForValue().get(username + ":" + agent);
            if (StringUtils.isEmpty(oldAccessToken)) {
                throw new SecurityAuthenticationException("agent not same");
            }
            redisTemplate.opsForValue().set(username + ":" + agent, token.getValue());
        }
        authenticationListener.refreshToken(request, token.getValue());

        return token;

    }

    protected String getClientId(HttpServletRequest request) throws IOException {
        String header = request.getHeader("Authorization");

        if (header == null || !header.startsWith("Basic ")) {
            throw new UnapprovedClientAuthenticationException("请求头中无client信息");
        }

        String[] tokens = oAuth2TokenUtils.extractAndDecodeHeader(header, request);
        assert tokens.length == 2;

        String clientId = tokens[0];
        return clientId;
    }


    public TokenRequest createTokenRequest(Map<String, String> requestParameters, ClientDetails authenticatedClient) {

        String clientId = requestParameters.get(OAuth2Utils.CLIENT_ID);
        if (clientId == null) {
            // if the clientId wasn't passed in in the map, we add pull it from the authenticated client object
            clientId = authenticatedClient.getClientId();
        } else {
            // otherwise, make sure that they match
            if (!clientId.equals(authenticatedClient.getClientId())) {
                throw new InvalidClientException("Given client ID does not match authenticated client");
            }
        }
        Set<String> grantTypes = authenticatedClient.getAuthorizedGrantTypes();
        if (!grantTypes.contains(REFRESH_TOKEN)) {
            throw new SecurityAuthenticationException("clientId not support refresh_token");
        }

        String grantType = requestParameters.get(OAuth2Utils.GRANT_TYPE);

        Set<String> scopes = OAuth2Utils.parseParameterList(requestParameters.get(OAuth2Utils.SCOPE));
        TokenRequest tokenRequest = new TokenRequest(requestParameters, clientId, scopes, grantType);

        return tokenRequest;
    }

    private boolean isRefreshTokenRequest(Map<String, String> parameters) {
        return REFRESH_TOKEN.equals(parameters.get(OAuth2Utils.GRANT_TYPE)) && parameters.get(REFRESH_TOKEN) != null;
    }


}
